#include "lom-transaction-system.hh"
#include "lom-transaction-events.hh"
#include "lom-errors.hh"

using namespace licada;
using namespace boost;

namespace lom
{
    static inline void deleteEvent( ILomTransactionEvent* evt ) {
	delete evt;
    }
    
    static inline void clearCacheEntry( TLomTransactionEventVectorUInt32Pair& pair ) {
	do_all( pair.first, bind( deleteEvent, _1 )) ;
	pair.first.clear();
	pair.second = 0;
    }
    static inline void doResetEntry( TLomTransactionEventVector::iterator& iter )
    {
	(*iter)->reset();
	++iter;
    }

    static inline void resetCacheEntry( TLomTransactionEventVectorUInt32Pair& pair ) {
	//Ensure we can't run off the end of the vector
	pair.second = std::min( pair.second, (uint32)pair.first.size() );
	//reset and iter the number of allocations we had.
	TLomTransactionEventVector::iterator iter = pair.first.begin();
	do_times( pair.second, bind( doResetEntry, ref(iter) ) );
	pair.second = 0;
    }

    template< typename TIterType, typename TActionType >
    inline void all_but( TIterType iter, TIterType end
			 , ILomTransactionHandler* activeHandler
			 , TActionType actionType )
    {
	for ( ; iter != end; ++iter )
	    if ( *iter != activeHandler )
		actionType( *iter );
    }
        
    LomTransactionSystem::LomTransactionSystem() 
	: valueFactory( NULL )
	  , activeHandler( NULL )
	  , eventSystem( NULL )
	
    {
	eventCache.resize( ETransactionEventTypeLast );
	do_all( eventCache, resetCacheEntry );
    }
    
    LomTransactionSystem::~LomTransactionSystem() 
    {
	resetLomTransactionSystem();
    }
    void LomTransactionSystem::resetLomTransactionSystem() {
	do_all( eventCache, clearCacheEntry );
    }
    
    void LomTransactionSystem::setupLomTransactionSystem( ILomValueFactory* _valueFactory
							  , ILomEventSystem* _eventSystem ) {
	valueFactory = _valueFactory;
	eventSystem = _eventSystem;
    }

	
    void LomTransactionSystem::addHandler( ILomTransactionHandler* handler ) {
	TLomTransactionHandlerVector::iterator find = std::find( handlers.begin(), handlers.end(), handler );
	if ( find == handlers.end() )
	    handlers.push_back( handler );
    }
    void LomTransactionSystem::removeHandler( ILomTransactionHandler* handler ) {
	erase_if( handlers, bind( std::equal_to<ILomTransactionHandler*>(), _1, handler ) );
    }
    //Begin listening to events and recording them
    void LomTransactionSystem::beginTransaction( ILomTransactionHandler* _activeHandler ) {
	if ( eventSystem == NULL )
	    throw InvalidLomTransactionSystemState();
	activeHandler = _activeHandler;
	currentEvents.clear();
	do_all( eventCache, resetCacheEntry );
	eventSystem->addEventHandler( this );
    }
    
    static inline void runActions( ILomTransactionHandler* handler, TLomTransactionEventVector& events ) {
	handler->beginTransactionEvents();
	//I should wrap this call heavily with exception handlers;
	//This is where 90% of all errors in the application will happen
	do_all( events, bind( &ILomTransactionHandler::handle, handler, _1 ) );
	handler->endTransactionEvents();
    }
    
    //Run through list and send events to everyone *but* the active handler.
    void LomTransactionSystem::endTransaction() 
    {
	if ( eventSystem == NULL )
	    throw InvalidLomTransactionSystemState();
	try
	{
	    if ( currentEvents.empty() == false ) {
		all_but( handlers.begin(), handlers.end(), activeHandler
			 , bind( runActions, _1, ref(currentEvents) ) );
	    }
	    currentEvents.clear();
	    do_all( eventCache, resetCacheEntry );
	    eventSystem->removeEventHandler( this );
	}
	catch( ... ) {
	    currentEvents.clear();
	    do_all( eventCache, resetCacheEntry );
	    eventSystem->removeEventHandler( this );
	    throw;
	}
    }
	


    void LomTransactionSystem::event( licada::TNameType name, const TValueVector& args )
    {
	LomTransactionEventEvent* evt = 
	    dynamic_cast<LomTransactionEventEvent*>(getOrCreateEvent( ETransactionEventTypeEvent ));
	nameStr = name;
	evt->setupEvent( nameStr, args, valueFactory );
	currentEvents.push_back( evt );
    }
    void LomTransactionSystem::classCreated( const LomClass& cls ) {
	LomTransactionClassCreatedEvent* evt = 
	    dynamic_cast<LomTransactionClassCreatedEvent*>(getOrCreateEvent( 
							       ETransactionEventTypeClassCreated ));
	evt->setupClassCreated( &cls );
	currentEvents.push_back( evt );
    }
    void LomTransactionSystem::instanceCreated( THandle instance ) {
	LomTransactionInstanceCreatedEvent* evt = 
	    dynamic_cast<LomTransactionInstanceCreatedEvent*>(getOrCreateEvent( 
								  ETransactionEventTypeInstanceCreated ));
	evt->setupInstanceEvent( instance );
	currentEvents.push_back( evt );
    }
    void LomTransactionSystem::instanceDestroyed( THandle instance ) {
	LomTransactionInstanceDestroyedEvent* evt = 
	    dynamic_cast<LomTransactionInstanceDestroyedEvent*>(getOrCreateEvent( 
								    ETransactionEventTypeInstanceDestroyed ));
	evt->setupInstanceEvent( instance );
	currentEvents.push_back( evt );
    }
    void LomTransactionSystem::propertyEvent( THandle instance
					      , licada::uint32 propIndex
					      , const LomValue& finalValue )
    {
	LomTransactionPropertyEvent* evt = 
	    dynamic_cast<LomTransactionPropertyEvent*>(getOrCreateEvent( 
								    ETransactionEventTypeInstanceProperty ));
	evt->setupPropertyEvent( instance, propIndex, &finalValue, valueFactory );
	currentEvents.push_back( evt );
    }
    /**
       The lom system defines a root context with a set of
       key-value pairs.  When something changes the root context,
       this event is sent.
    */
    void LomTransactionSystem::rootObjectSet( licada::TNameType name, const LomValue& value )
    {
	LomTransactionRootObjectEvent* evt = 
	    dynamic_cast<LomTransactionRootObjectEvent*>(getOrCreateEvent( 
								    ETransactionEventTypeRootObject ));
	nameStr = name;
	evt->setupRootObjectEvent( nameStr, &value, valueFactory );
	currentEvents.push_back( evt );
    }

    ILomTransactionEvent* LomTransactionSystem::getOrCreateEvent( ETransactionEventType type ) 
    {
	TLomTransactionEventVectorUInt32Pair& data = eventCache[type];
	if ( data.first.empty() || data.second >= data.first.size() ) {
	    
	    data.second = data.first.size();
	    
	    switch( type )
	    {
	    case ETransactionEventTypeEvent:
		data.first.push_back( new LomTransactionEventEvent() );
		break;
	    case ETransactionEventTypeClassCreated:
		data.first.push_back( new LomTransactionClassCreatedEvent() );
		break;
	    case ETransactionEventTypeInstanceCreated:
		data.first.push_back( new LomTransactionInstanceCreatedEvent() );
		break;
	    case ETransactionEventTypeInstanceDestroyed:
		data.first.push_back( new LomTransactionInstanceDestroyedEvent() );
		break;
	    case ETransactionEventTypeInstanceProperty:
		data.first.push_back( new LomTransactionPropertyEvent() );
		break;
	    case ETransactionEventTypeRootObject:
		data.first.push_back( new LomTransactionRootObjectEvent() );
		break;
	    default:
		throw UnrecognizedTransactionEventType( type );
	    }
	}
	
	ILomTransactionEvent* hdl = data.first[data.second];
	++data.second;
	return hdl;
    }
}
